module Orator

  # This provides openstruct-like access to a hash behind the scenes.  This
  # class does not dynamically define methods on itself.
  #
  # @!attribute [r] table
  #   The table that the class uses behind the scenes.
  #
  #   @return [Hash]
  class OpenStruct

    # Initialize.
    #
    # @param hash [Hash] the hash to intialize with.
    def initialize(hash = {})
      @table = hash.dup
    end

    # This provides access to the table without raising an error.
    #
    # @param key [Object] the key of the element we're looking for.
    # @return [Object, nil]
    def [](key)
      @table[key.to_s]
    end

    # This provides access to the table without raising an error.
    #
    # @param key [Object] the key of the element we're going to set.
    # @param value [Object] the element we're going to set the key to.
    def []=(key, value)
      puts "Assigning #{value.inspect} to :#{key}"
      @table[key.to_s] = value
    end

    # This returns the table that we're using.
    #
    # @private
    def table
      @table.dup
    end

    # This handles the magic of this class.  If the method doesn't end in `=`,
    # it checks to see if the element exists in the table; if not, it calls
    # `super`.  Otherwise, it sets the value and carries on.  It ignores blocks
    # given to it.  If too many arguments are passed, it calls `super`.
    #
    # @param method [Symbol] the method to look up.
    # @param args [Array<Object>]
    # @return [Object]
    def method_missing(method, *args)
      method = method.to_s
      raise ArgumentError if args.length > 1

      if method =~ /\=\z/
        self[method[0..-2]] = args[0]
      else
        raise ArgumentError if args.length > 0
        super unless @table.key?(method)
        self[method]
      end
    end

    # This checks to see if we can respond to the method.  This is used in
    # {#respond_to?} to let other objects know we do respond to that method, as
    # well as ruby internally for the {#method} method.
    def respond_to_missing?(method, include_private = false)
      method = method.to_s

      if method =~ /\=\z/
        true
      else
        @table.key?(method)
      end
    end

  end
end
